home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fatted Calf
/
The Fatted Calf.iso
/
Applications
/
Communication
/
NewsBase
/
Source
/
INntpIO.m
< prev
next >
Wrap
Text File
|
1993-01-12
|
41KB
|
1,294 lines
/*$Copyright:
* Copyright (C) 1992.5.22. Recruit Co.,Ltd.
* Institute for Supercomputing Research
* All rights reserved.
* NewsBase by ISR, Kazuto MIYAI, Gary ARAKAKI, Katsunori SUZUKI, Kok-meng Lue
*
* You may freely copy, distribute and reuse the code in this program under
* following conditions.
* - to include this notice in the source code, if it is to be distributed
* with source code.
* - to add the file named "COPYING" within the code, which shall include
* GNU GENERAL PUBLIC LICENSE(*).
* - to display an acknowledgement in binary code as follows: "This product
* includes software developed by Recruit Co.,Ltd., ISR."
* - to display a notice which shall state that the users may freely copy,
* distribute and reuse the code in this program under GNU GENERAL PUBLIC
* LICENSE(*)
* - to indicate the way to access the copy of GNU GENERAL PUBLIC LICENSE(*)
*
* (*)GNU GENERAL PUBLIC LICENSE is stored in the file named COPYING
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
$*/
#import "IOrderedListD.h"
#import "InfoD.h"
#import "INewsgroupInfoD.h"
#import "IOrderedListD.h"
#import "INntpIO.h"
#import "ITreeNodeD.h"
#import "errdebug.h"
#import "data_types.h"
#import "IUifNode.h"
#import <appkit/appkit.h>
#import <stdio.h>
#import <ctype.h>
#import <libc.h>
#import <string.h>
#import <mach/mach.h>
#import <objc/zone.h>
#import "Localization.h"
#define LoStr(key) doLocalString(NULL,key,NULL)
static HashTable *tableOfNoiseWords;
static const char *noiseWords[] = {
#include "noise_words.h"
};
#define SIZEMARKONE "sizemarkOne"
#define SIZEMARKTWO "sizemarkTwo"
#define SIZEMARKTHREE "sizemarkThree"
static const char *ikey[MAX_NO_OF_TOKENS];
@implementation INntpIO
+ initialize
{
char filename[512];
NXStream *stream;
char *buffer;
int len, maxlen;
char *ptr;
const char **word;
tableOfNoiseWords = [[HashTable alloc] initKeyDesc:"*"];
sprintf(filename, "%.491s/.NewsBaseNoiseWords", getenv("HOME"));
if ((stream = NXMapFile(filename, NX_READWRITE)) != NULL) {
NXSeek(stream, (long)0, NX_FROMEND);
NXPutc(stream, '\0');
NXGetMemoryBuffer(stream, &buffer, &len, &maxlen);
for (ptr = strtok(buffer, " \n\r"); ptr != NULL;
ptr = strtok(NULL, " \n\r")) {
[tableOfNoiseWords insertKey:ptr value:nil];
}
NXCloseMemory(stream, NX_SAVEBUFFER);
return(self);
} else {
for (word = noiseWords; *word != NULL; ++word) {
[tableOfNoiseWords insertKey:*word value:nil];
}
return(self);
}
}
- initServer:(char *)nntpHost allNews:(BOOL)allNewsFlag
newsGroupMode:(BrowserMode)g_mode
articleMode:(BrowserMode)a_mode
{
const char *buffer;
iNewsGroupMode = g_mode;
iArticleMode = a_mode;
irFlag = UNMARKEDONLY;
// set default
iBoundaryForTwo = 100; iBoundaryForThree = 300;
// read default database and set boundary Line # for line mark
if ((buffer = NXGetDefaultValue(OWNER, ARTICLESIZEMARK)) != 0) {
sscanf(buffer, "0:%d:%d", &iBoundaryForTwo, &iBoundaryForThree);
}
DBG(1, fprintf(stderr,"boundaryTwo = %d\t Tree = %d\n",
iBoundaryForTwo, iBoundaryForThree));
return [super initServer:nntpHost allNews:allNewsFlag];
}
- subDirectoryOf:knode
{
/* directory structure of newsgroup tree */
/* */
/* [iNewsGroupTreeRoot]-+-[tree node]-+-[newsgroupInfo] */
/* | +-[tree node]-[newsgroupInfo]*/
/* +-[tree node]+[tree node] */
if (iNewsGroupMode == Tree) {
return [self _subDirectoryOf:knode];
} else {
return [self _flatDirectoryOf:knode];
}
}
- _subDirectoryOf:knode
{
id dirNewsGroup, childNewsGroup;
id listnode;
id node;
id n;
int i, j;
int count;
char *subscribe;
id groupInfo;
char *newsgroupName;
NXTreeState state;
id tnode;
/* set newsgroup Tree node */
if (knode == nil) {
/* dirNode is rootNode */
dirNewsGroup = iNewsGroupTreeRoot;
} else {
dirNewsGroup = [knode linkedData];
}
listnode = [[IOrderedListD allocFromZone:[self zone]]
initWithKey:"listnode"];
for (i=0; childNewsGroup=[dirNewsGroup objectAt:i]; ++i) {
if ([childNewsGroup isMemberOf:[ITreeNodeD class]]==NO) {
/* childNewsGroup is not a real newsgroup Tree node */
continue;
}
// set leaf or not
count = 0;
for (j=0; n=[childNewsGroup objectAt:j]; ++j) {
if ([n isMemberOf:[ITreeNodeD class]]==NO) {
continue;
}
++count;
}
// a childNewsGroup is classified into 3 types
// 1. directory
// 2. leaf and directory
// childNewsGroup has tree node and newsgroup info
// 3. leaf
// 1. directory only
if ((count > 0) && [childNewsGroup dataForKey:GROUPINFO] == nil) {
node = [[IUifNode allocFromZone:[self zone]]
initWithKey:[childNewsGroup key]];
[listnode addObject:node];
[node setTitleForCell:[childNewsGroup key]];
[node setLinkedData:childNewsGroup];
[node setNodeType:DirOfSubDirs];
[node setLeaf:NO];
// check newsgroup is subscribed or not here to
// set directory active or notactive
[node setActive:NO]; // default is not active
[childNewsGroup initState:&state];
while (tnode = [childNewsGroup nextState:&state]) {
if ((strcmp([tnode key], GROUPINFO) == 0)
&& (strcmp([tnode infoForKey:SUBSCRIBE], "yes") == 0 )) {
// found a active newsgroup under me
[node setActive:YES];
break;
}
}
} else if (count > 0) {
// 2. leaf and directory
// insert leaf cell
//
node = [[IUifNode allocFromZone:[self zone]]
initWithKey:[childNewsGroup key]];
[listnode addObject:node];
[node setTitleForCell:[childNewsGroup key]];
[node setLinkedData:childNewsGroup nodeType:DirOfItems];
[node setLeaf:YES];
// set image
if ((groupInfo=[childNewsGroup dataForKey:GROUPINFO]) != nil) {
if ([groupInfo isAllArticleMarked] == NO) {
[node setImageForCell:
[NXImage findImageNamed:UNREADMARK]];
}
}
/* set newsgroup name for dataGroupName */
[node setDataGroupName:[[childNewsGroup dataForKey:GROUPINFO]
infoForKey:GROUPNAME]];
DBG(10,fprintf(stderr,"newsgroupName=%s",newsgroupName));
/* set node active for subscribe, no active for unsubscribe */
subscribe = [[childNewsGroup dataForKey:GROUPINFO]
infoForKey:SUBSCRIBE];
if(strncmp(subscribe,"y",1)==0) {
[node setActive:YES];
} else {
[node setActive:NO];
}
// insert directory cell
//
node = [[IUifNode allocFromZone:[self zone]]
initWithKey:[childNewsGroup key]];
[listnode addObject:node];
[node setTitleForCell:[childNewsGroup key]];
[node setLinkedData:childNewsGroup];
[node setNodeType:DirOfSubDirs];
[node setLeaf:NO];
// check newsgroup is subscribed or not here to
// set directory active or notactive
[node setActive:NO]; // default is not active
[childNewsGroup initState:&state];
while (tnode = [childNewsGroup nextState:&state]) {
if ((strcmp([tnode key], GROUPINFO) == 0)
&& (strcmp([tnode infoForKey:SUBSCRIBE], "yes") == 0 )) {
[node setActive:YES];
break;
}
}
} else {
// 3. leaf
node = [[IUifNode allocFromZone:[self zone]]
initWithKey:[childNewsGroup key]];
[listnode addObject:node];
[node setTitleForCell:[childNewsGroup key]];
[node setLinkedData:childNewsGroup];
[node setNodeType:DirOfItems];
[node setLeaf:YES];
if ((groupInfo=[childNewsGroup dataForKey:GROUPINFO]) != nil) {
if ([groupInfo isAllArticleMarked] == NO) {
[node setImageForCell:[NXImage findImageNamed:UNREADMARK]];
}
}
[node setDataGroupName:[[childNewsGroup dataForKey:GROUPINFO]
infoForKey:GROUPNAME]];
subscribe = [[childNewsGroup dataForKey:GROUPINFO]
infoForKey:SUBSCRIBE];
if(strncmp(subscribe,"y",1)==0) {
[node setActive:YES];
} else {
[node setActive:NO];
}
}
}
return listnode;
}
- _flatDirectoryOf:knode
{
id newsGroup;
id listnode, node;
NXTreeState state;
// const char *key;
char *subscribe;
id treenode;
char *newsgroupName;
id groupInfo;
/* set newsgroup Tree node */
/* only rebuild from root(==nil) for now */
if (knode == nil) {
/* dirNode is rootNode */
newsGroup = iNewsGroupTreeRoot;
} else {
newsGroup = [knode linkedData];
}
listnode = [[IOrderedListD allocFromZone:[self zone]]
initWithKey:"listnode"];
[newsGroup initState:&state];
while (treenode=[newsGroup nextState:&state]) {
if ([treenode isMemberOf:[ITreeNodeD class]]==NO) {
/* newsGroup is not a real newsgroup Tree node */
continue;
}
if ([treenode dataForKey:GROUPINFO]==nil) {
/* this node is only for grouping node, not have newsgroup inof */
continue;
}
newsGroup = treenode;
newsgroupName = [[newsGroup dataForKey:GROUPINFO] infoForKey:GROUPNAME];
node = [[IUifNode allocFromZone:[self zone]] initWithKey:newsgroupName];
[node setTitleForCell:newsgroupName];
[node setLinkedData:newsGroup nodeType:DirOfItems];
[node setDataGroupName:newsgroupName];
[node setLeaf:YES]; /* flat's cell is always leaf */
// set image
if ((groupInfo=[newsGroup dataForKey:GROUPINFO]) != nil) {
if ([groupInfo isAllArticleMarked] == NO) {
[node setImageForCell:[NXImage findImageNamed:UNREADMARK]];
}
}
subscribe = [[newsGroup dataForKey:GROUPINFO] infoForKey:SUBSCRIBE];
if(strncmp(subscribe,"y",1)==0) {
[node setActive:YES];
} else {
[node setActive:NO];
}
[listnode addObject:node];
}
return listnode;
}
- itemHeadersOf:knode
{
/* itemHeadersOf */
/* from newsgroup browser: click on leaf cell of newsgroup browser */
/* from article browser: click on article directory */
id linkdata;
if ((linkdata = [knode linkedData]) == nil) {
return 0;
}
switch ([knode nodeType]) {
case DirOfItems:
/* linkdata has groupInfo, so coming from NewsGroup browser */
switch(iArticleMode) {
case Flat:
/* get headers from newsgroup info */
return [self _itemHeadersOf:knode];
case Tree:
/* Tree mode */
/* clicked on newsgroup browser, create article reference tree */
/* make iArticleDB with article headers */
[self _itemHeadersOf:knode];
/* make articleTree from iArticleDB */
[self _makeArticleTree];
/* return listnode for column-0 for ReferenceMode */
return [self _itemTreeHeadersOf:knode];
case BySubject:
/* return listnode for column-0 for BySubjectMode */
return([self _subjectHeadersOf:knode]);
case ByKeyword:
/* return listnode for column-0 for ByKeywordMode */
return([self _keywordHeadersOf:knode]);
}
case ReferenceGroup:
/* Article Browser should be in Tree Mode */
return [self _itemSubTreeHeadersOf:knode];
case SubjectGroup:
case KeywordGroup:
/* Article Browser should be in BySubject or ByKeyword Mode */
return([self _itemHeadersOfSubject:knode]);
default:
return(0);
}
}
- _itemTreeHeadersOf:knode
{
id listnode, node;
int i, j, k;
id childArticle, articleItem, g_child, gg_child;
id last_articleGroup;
char *subject;
int tree_node_count;
id groupInfo, headerInfo;
const char *newsgroupName;
char *groupnameTmp;
char *cline;
id image;
BOOL isUnreadArticle;
/* _itemTreeHeadersOf is for filling cell in colum-0 of article browser */
/* _itemSubTreeHeadersOf is for column-1 */
/* return listnode for column-0 */
listnode = [[IOrderedListD allocFromZone:[self zone]]
initWithKey:"listnode"];
// search groupInfo
groupnameTmp = NXCopyStringBuffer([knode dataGroupName]);
makeTreeKey (groupnameTmp, ikey, ".");
groupInfo=[[iNewsGroupTreeRoot nodeForKey:ikey]
dataForKey:GROUPINFO];
free(groupnameTmp);
/* iArticleReferenceTreeRoot sould be made befor here */
for (i=0;childArticle=[iArticleReferenceTreeRoot objectAt:i];++i) {
/* iArticleReferenceTreeRoot strucrture */
/* (1) case 1 */
/*[root]--+-[tree_node]---[articleItem]-[header] */
/* |(childArticle) */
/* (2) case 2 */
/* +-[tree_node]-+-[articleItem]-[header] */
/* | | */
/* | +-[tree_node]-[articleItem]-[header] */
/* | +-[tree_node]-[articleItem]-[header] */
/* (3) case 3 */
/* (original article is already removed in server) */
/* +-[tree_node]-+ */
/* +-[tree_node]-[articleItem]-[header] */
/* +-[tree_node]-[articleItem]-[header] */
if ([childArticle isMemberOf:[ITreeNodeD class]]==NO) {
/* childArticle must be ITreeNodeD */
/* if you go into here, something wrong */
fprintf (stderr,"INntpIO: error: childArticle is not a member of ITreeNodeD\n");
continue;
}
/* search articleItem, count tree_node, set title for cell */
/* tree_node does not */
/* have articleItem, use articleItem of last child of */
/* this node as articleItem for tree_node */
articleItem = nil; /* articleItem */
tree_node_count = 0;
isUnreadArticle = NO;
for (j=0; g_child=[childArticle objectAt:j]; ++j) {
if ([g_child isMemberOf:[IOrderedListD class]] &&
([g_child dataForKey:HEADER_INFO]!=nil)) {
/* articleItem have header.tbl */
/* case (1) or (2) */
articleItem = g_child;
node = [[IUifNode allocFromZone:[self zone]]
initWithKey:[articleItem key]];
[listnode addObject:node];
[node setLeaf:YES];
[node setLinkedData:articleItem nodeType:Header];
newsgroupName = [knode dataGroupName];
[node setDataGroupName:newsgroupName];
headerInfo = [g_child dataForKey:HEADER_INFO];
subject = [headerInfo infoForKey:SUBJECT];
// set title and image
[node setTitleForCell:subject];
if ((cline=(char *)[headerInfo infoForKey:LINES]) != NULL) {
// article header has "Lines:", so set mark to cell
image = [self getArticleMarkFor:(int)atoi(cline)];
[node setImageForCell:image];
}
/* check article is already read or not */
if ([groupInfo checkArticleIsRead:
(int)[headerInfo infoForKey:ARTICLE_NUM]]) {
[node setActive:NO];
} else {
[node setActive:YES];
}
} else if ([g_child isMemberOf:[ITreeNodeD class]]==YES) {
/* g_child is tree_node */
/* case (2) or (3) */
last_articleGroup = g_child;
++ tree_node_count;
// check if this g_child's article is already read
for (k=0; gg_child=[last_articleGroup objectAt:k]; ++k) {
if ([gg_child isMemberOf:[IOrderedListD class]] &&
((headerInfo=[gg_child dataForKey:HEADER_INFO])!=nil)) {
// gg_child is articleItem
if ([groupInfo checkArticleIsRead:
(int)[headerInfo infoForKey:ARTICLE_NUM]] == NO) {
// there is an unread article
isUnreadArticle = YES;
break;
}
}
}
}
}
if (articleItem == nil) {
/* case (3), can't find right article */
/* g_child is a last article of this article group */
for (j=0; gg_child=[last_articleGroup objectAt:j]; ++j) {
if ([gg_child isMemberOf:[IOrderedListD class]] &&
([gg_child dataForKey:HEADER_INFO]!=nil)) {
articleItem = gg_child;
break;
}
}
}
subject = [[articleItem dataForKey:HEADER_INFO] infoForKey:SUBJECT];
if (tree_node_count >0 ) {
node = [[IUifNode allocFromZone:[self zone]]
initWithKey:[childArticle key]];
[listnode addObject:node];
[node setTitleForCell:subject];
[node setLeaf:NO];
[node setLinkedData:childArticle nodeType:ReferenceGroup];
[node setDataGroupName:[knode dataGroupName]];
// check if all article is read or not to set directory's active
if (isUnreadArticle == NO) {
// child node is all not active, so directory is not active
[node setActive:NO];
} else {
[node setActive:YES];
}
}
}
DBG(20, {int i; id child; for(i=0;child=[listnode objectAt:i];++i) {
fprintf(stderr,"child key=%s\n",[child key]);} });
return listnode;
}
- _itemSubTreeHeadersOf:knode
{
id listnode, node;
id articleGroup;
id articleItem, headerInfo, groupInfo;
id childArticle, g_child;
int i, j;
const char *newsgroupName;
// char groupnameTmp[256];
char *groupnameTmp;
char *cline;
id image;
articleGroup = [knode linkedData];
/* articleGroup is [tree_node], represent article group node */
listnode = [[IOrderedListD allocFromZone:[self zone]]
initWithKey:"listnode"];
for (i=0; childArticle=[articleGroup objectAt:i]; ++i) {
/* (articleItem sould be displayed in */
/* column-0) */
/* +-[tree_node]-+-([articleItem]-[header]) */
/* | */
/* +-[tree_node]-[articleItem]-[header] */
/* +-[tree_node]-[articleItem]-[header] */
if ([childArticle isMemberOf:[ITreeNodeD class]]==NO) {
/* only [tree_node] are under article group node */
continue;
}
for (j=0; g_child=[childArticle objectAt:j]; ++j) {
if ([g_child isMemberOf:[IOrderedListD class]] &&
((headerInfo=[g_child dataForKey:HEADER_INFO])!=nil)) {
/* g_child is article */
articleItem = g_child;
node = [[IUifNode allocFromZone:[self zone]]
initWithKey:[articleItem key]];
[listnode addObject:node];
[node setTitleForCell:[headerInfo infoForKey:MESSAGE_ID]];
if ((cline=(char *)[headerInfo infoForKey:LINES]) != NULL) {
// article header has "Lines:", so set mark to cell
image = [self getArticleMarkFor:(int)atoi(cline)];
[node setImageForCell:image];
}
[node setLinkedData:articleItem nodeType:Header];
newsgroupName = [knode dataGroupName];
[node setDataGroupName:newsgroupName];
[node setLeaf:YES];
/* check article is already read or not */
//strncpy(groupnameTmp, newsgroupName, sizeof(groupnameTmp)-1);
groupnameTmp = NXCopyStringBuffer(newsgroupName);
makeTreeKey(groupnameTmp, ikey, ".");
groupInfo=[[iNewsGroupTreeRoot nodeForKey:ikey]
dataForKey:GROUPINFO];
free(groupnameTmp);
if ([groupInfo checkArticleIsRead:
(int)[headerInfo infoForKey:ARTICLE_NUM]]) {
[node setActive:NO];
} else {
[node setActive:YES];
}
}
}
}
return listnode;
}
- _itemHeadersOf:knode
{
id newsGroup;
id listnode;
id node;
id article;
id headerInfo, groupInfo;
int i;
const char *newsgroupName;
// char groupnameTmp[256];
char *groupnameTmp;
char *message_id;
id image;
char *cline;
newsGroup = [knode linkedData];
iArticleDB = [self initNewsGroup:newsGroup readFlag:(ReadFlag)irFlag];
listnode = [[IOrderedListD allocFromZone:[self zone]]
initWithKey:"listnode"];
for (i=0; article=[iArticleDB objectAt:i]; ++i) {
headerInfo = [article dataForKey:HEADER_INFO];
message_id = [headerInfo infoForKey:MESSAGE_ID];
/* if no message_id, bring up alert panel and skip it */
if (message_id == NULL) {
NXRunAlertPanel(LoStr("NewsBase"),LoStr("no Message-ID field")
,LoStr("OK"),NULL,NULL);
continue;
}
node = [[IUifNode allocFromZone:[self zone]] initWithKey:message_id];
[listnode addObject:node];
[node setTitleForCell:[headerInfo infoForKey:SUBJECT]];
if ((cline=(char *)[headerInfo infoForKey:LINES]) != NULL) {
// article header has "Lines:", so set mark to cell
image = [self getArticleMarkFor:(int)atoi(cline)];
[node setImageForCell:image];
}
[node setLinkedData:article nodeType:Header];
newsgroupName = [knode dataGroupName];
[node setDataGroupName:newsgroupName];
[node setLeaf:YES];
/* check article is already read or not */
//strncpy(groupnameTmp, newsgroupName, sizeof(groupnameTmp)-1);
groupnameTmp = NXCopyStringBuffer(newsgroupName);
makeTreeKey(groupnameTmp, ikey, ".");
groupInfo=[[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO];
free(groupnameTmp);
if ([groupInfo checkArticleIsRead:
(int)[headerInfo infoForKey:ARTICLE_NUM]]) {
[node setActive:NO];
} else {
[node setActive:YES];
}
}
return listnode;
}
- getArticleMarkFor:(int)linenum
{
id image;
if (linenum < iBoundaryForTwo) {
image = [NXImage findImageNamed:SIZEMARKONE];
} else if (iBoundaryForTwo <= linenum && linenum < iBoundaryForThree) {
image = [NXImage findImageNamed:SIZEMARKTWO];
} else if (iBoundaryForThree <= linenum ) {
image = [NXImage findImageNamed:SIZEMARKTHREE];
} else {
// linenum <= 0, and other
image = NULL;
}
return image;
}
- itemOf:knode
{
IOrderedListD *article;
InfoD *header;
const char *messageId;
if ((article = [knode linkedData]) == nil) {
return(nil);
}
if ((header = [article dataForKey:HEADER_INFO]) == nil) {
return(nil);
}
messageId = (const char *)[header infoForKey:MESSAGE_ID];
if ([self sendArticle:messageId] == YES) {
return(self);
} else {
return(nil);
}
}
- (BOOL)toggleActive:knode
{
id newsGroup, groupInfo;
id headerInfo;
int art_num;
const char *newsgroupName;
char *groupnameTmp;
enum {ACTIVATE, NOTACTIVATE} activateAction;
char *xref, buf[MAXPATHLEN], *bufptr[MAX_NO_OF_TOKENS];
char groupNameBuf[MAXPATHLEN];
int i;
NXTreeState state;
id rootnode, tnode;
DBG(1, fprintf(stderr,"INntpIO: -- toggleActive fired\n"));
switch ([knode nodeType]) {
/* message is coming from newsgroup browser */
/* toggle subscribe */
case DirOfItems:
// selected cell is a newsgroup leaf cell
DBG(10,fprintf(stderr,"-- toggle SUBSCRIBE"));
groupInfo = [[knode linkedData] dataForKey:GROUPINFO];
if ([knode active]) {
/* knode is active, subscribe -> unsubscribe */
[groupInfo addInfoString:"no" key:SUBSCRIBE];
} else {
/* knode is not active, unsubscribe -> subscribe */
[groupInfo addInfoString:"yes" key:SUBSCRIBE];
}
return YES;
break;
case DirOfSubDirs:
// selected cell is a directory of newsgroup
// subscribe or unsubscribe all newsgroup under this directory
activateAction = ([knode active]) ? NOTACTIVATE : ACTIVATE;
rootnode = [knode linkedData];
[rootnode initState:&state];
while (tnode=[rootnode nextState:&state]) {
if (strcmp([tnode key], GROUPINFO) == 0) {
// tnode is groupInfo for newsgroup
if (activateAction == NOTACTIVATE) {
[tnode addInfoString:"no" key:SUBSCRIBE];
} else {
[tnode addInfoString:"yes" key:SUBSCRIBE];
}
}
}
return YES;
break;
/* message is coming from article header browser */
/* toggle article mark */
case ReferenceGroup:
// selected cell is a directory of article
// knode is obj. of "ITreeNodeD"
activateAction = ([knode active]) ? NOTACTIVATE : ACTIVATE;
rootnode = [knode linkedData];
[rootnode initState:&state];
while (tnode=[rootnode nextState:&state]) {
if ([tnode isMemberOf:[IOrderedListD class]] &&
((headerInfo=[tnode dataForKey:HEADER_INFO]) != nil)) {
// tnode is article
art_num = (int)[headerInfo infoForKey:ARTICLE_NUM];
groupnameTmp = NXCopyStringBuffer([knode dataGroupName]);
makeTreeKey(groupnameTmp, ikey, ".");
newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey];
free(groupnameTmp);
groupInfo = [newsGroup dataForKey:GROUPINFO];
if (activateAction == NOTACTIVATE) {
// this directory cell is active so mark all article
// as read
[groupInfo markReadArticle:art_num];
} else {
[groupInfo unmarkReadArticle:art_num];
}
}
}
return YES;
break;
case SubjectGroup:
case KeywordGroup:
// selected cell is a directory of article
// knode is obj. of "IOrderedListD"
activateAction = ([knode active]) ? NOTACTIVATE : ACTIVATE;
rootnode = [knode linkedData];
for (i=0; tnode=[rootnode objectAt:i]; ++i) {
if ([tnode isMemberOf:[IOrderedListD class]] &&
((headerInfo=[tnode dataForKey:HEADER_INFO]) != nil)) {
// tnode is article
art_num = (int)[headerInfo infoForKey:ARTICLE_NUM];
groupnameTmp = NXCopyStringBuffer([knode dataGroupName]);
makeTreeKey(groupnameTmp, ikey, ".");
newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey];
free(groupnameTmp);
groupInfo = [newsGroup dataForKey:GROUPINFO];
if (activateAction == NOTACTIVATE) {
// selected cell is active
// mark all article as read under this directory
[groupInfo markReadArticle:art_num];
} else {
[groupInfo unmarkReadArticle:art_num];
}
}
}
return YES;
break;
case Header:
// selected cell is a leaf cell of article
DBG(1,fprintf(stderr,"-- toggle article mark"));
headerInfo = [[knode linkedData] dataForKey:HEADER_INFO];
// mark this article first
newsgroupName = [knode dataGroupName];
groupnameTmp = NXCopyStringBuffer(newsgroupName);
makeTreeKey(groupnameTmp, ikey, ".");
newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey];
free(groupnameTmp);
groupInfo = [newsGroup dataForKey:GROUPINFO];
// set this article's article number and mark or unmark it
// activateAction will be used for Xref: things
art_num = (int)[headerInfo infoForKey:ARTICLE_NUM];
if ([groupInfo checkArticleIsRead:art_num]) {
/* article is already read, mark -> unmark */
[groupInfo unmarkReadArticle:art_num];
activateAction = NOTACTIVATE;
} else {
[groupInfo markReadArticle:art_num];
activateAction = ACTIVATE;
}
// do Xref: things
if ((xref=(char *)[headerInfo infoForKey:"Xref"]) == NULL) {
// this article does not have cross reference
return YES;
} else {
// article has Xref:
strncpy(buf, xref, sizeof(buf));
makeTreeKey(buf, bufptr, " ");
for (i=1; bufptr[i] != NULL; ++i) {
// skip first one (xxxxx)
// assume that Xref: field is like
// xxxxx aaa.aaa.aaa:nnn bbb.bbb.bbb:mmm .....
// xxxxx: e.g. ISRNEWS
// aaa.aaa.aaa, bbb.bbb.bbb: newsgroup
// nnn, mmm: article number
if (sscanf(bufptr[i],"%1023[^:]:%d",groupNameBuf,&art_num)==2){
// buf is newsgroup name
DBG(1, fprintf(stderr,"newsgroup=%s\t art_num=%d\n",
groupNameBuf, art_num));
makeTreeKey(groupNameBuf, ikey, ".");
newsGroup = [iNewsGroupTreeRoot nodeForKey:ikey];
groupInfo = [newsGroup dataForKey:GROUPINFO];
if (activateAction == NOTACTIVATE) {
[groupInfo unmarkReadArticle:art_num];
} else {
[groupInfo markReadArticle:art_num];
}
}
}
return YES;
}
break;
default:
// should never coming here
return NO;
break;
}
return NO;
}
- (BrowserMode)toggleNewsGroupMode:sender
{
switch ((BrowserMode)[[sender selectedCell] tag]) {
case Flat:
iNewsGroupMode = Flat;
break;
case Tree:
default:
iNewsGroupMode = Tree;
break;
}
return iNewsGroupMode;
}
- (BrowserMode)toggleArticleMode:sender
{
switch ((BrowserMode)[[sender selectedCell] tag]) {
case Flat:
iArticleMode = Flat;
break;
case Tree:
iArticleMode = Tree;
break;
case BySubject:
iArticleMode = BySubject;
break;
case ByKeyword:
iArticleMode = ByKeyword;
break;
}
return(iArticleMode);
}
- setArticleMode:(BrowserMode)articleMode
{
iArticleMode = articleMode;
return(self);
}
- setReadFlag:(ReadFlag)rflag
{
irFlag = rflag;
return self;
}
- _makeArticleTree
{
int i;
id articleItem;
iArticleHashTable = [[HashTable allocFromZone:headersZone]
initKeyDesc:"*" valueDesc:"@"];
iArticleReferenceTreeRoot = [[ITreeNodeD allocFromZone:headersZone]
initWithKey:"root"];
for (i=0; articleItem=[iArticleDB objectAt:i]; ++i) {
[iArticleHashTable insertKey:[[articleItem dataForKey:HEADER_INFO]
infoForKey:MESSAGE_ID] value:articleItem];
}
[self _addArticleToTree];
return iArticleReferenceTreeRoot;
}
- (void)_addArticleToTree
{
#define TREELISTSIZE 128
char treeKeyList[TREELISTSIZE];
char treeKeyListTmp[TREELISTSIZE];
const char *mes_id;
id node;
NXHashState state;
id articleItem;
id org_article;
char *org_art_messageID;
int org_art_num;
char *key[MAX_NO_OF_TOKENS];
state = [iArticleHashTable initState];
while ([iArticleHashTable nextState:&state
key:(const void **)&mes_id value:(void **)&articleItem]) {
/* look for original article's message_id */
if ((org_art_messageID=[self findOriginalArticle:articleItem])==NULL) {
/* original article */
sprintf(treeKeyList, "%010d",
(int)[[articleItem dataForKey:HEADER_INFO] infoForKey:ARTICLE_NUM]);
} else {
/* article has reference */
if ((org_article = [iArticleHashTable
valueForKey:(char *)org_art_messageID]) == NULL) {
/* if original aritlce is not in server,
key = "0000000000_messageID 0000000yyy" */
sprintf(treeKeyList, "0000000000_%.105s %010d",
(char *)org_art_messageID,
(int)[[articleItem dataForKey:HEADER_INFO]
infoForKey:ARTICLE_NUM]);
} else {
/* if original article is in server,
key= "0000000xxx 0000000yyy" */
org_art_num = (int)[[org_article dataForKey:HEADER_INFO]
infoForKey:ARTICLE_NUM];
sprintf(treeKeyList, "%010d %010d", org_art_num,
(int)[[articleItem dataForKey:HEADER_INFO]
infoForKey:ARTICLE_NUM]);
}
DBG(10,fprintf(stderr,"treeKeyList = %s\n org_subject = %s\n"
"Subject = %s", treeKeyList,
(char *)[[org_article dataForKey:HEADER_INFO] infoForKey:SUBJECT],
(char *)[[articleItem dataForKey:HEADER_INFO] infoForKey:SUBJECT]));
}
strncpy(treeKeyListTmp, treeKeyList, sizeof(treeKeyListTmp)-1);
makeTreeKey(treeKeyListTmp, key, " ");
node = [iArticleReferenceTreeRoot addNodeForKey:key];
[node insertKeyedObject:articleItem];
}
}
- (char *)findOriginalArticle:articleItem
{
char *headRef, *references;
char *ch;
if (([[articleItem dataForKey:HEADER_INFO]
infoForKey:REFERENCES]) == NULL) {
/* if not responsed article */
return NULL;
}
do {
if((references=[[articleItem dataForKey:HEADER_INFO]
infoForKey:REFERENCES]) == NULL) {
return headRef;
}
headRef = references;
if ((ch=strchr(headRef,' ')) != NULL) {
*ch = '\0';
} else {
/* if not ' ' in headRef, only one message id
is in references field , and that's original article */
return headRef;
}
} while (articleItem=[iArticleHashTable valueForKey:headRef]);
return headRef;
}
- (BrowserMode)newsGroupMode
{
return iNewsGroupMode;
}
- (BrowserMode)articleMode
{
return(iArticleMode);
}
- (IOrderedListD *)_subjectHeadersOf:(IUifNode *)newsgroupNode
{
int i, k;
IOrderedListD *articleItem;
InfoD *header;
const char *subject;
int articleNo;
char articleNoString[16];
IOrderedListD *listnode;
HashTable *tableOfSubjectGroups;
NXHashState tableState;
IOrderedListD *subjectGroup;
IUifNode *subjectNode;
IUifNode *articleNode;
const char *newsgroupName;
char *groupnameTmp;
char *cline;
id image;
BOOL isUnreadArticle;
INewsgroupInfoD *groupInfo;
id c_article, c_header;
iArticleDB = [self initNewsGroup:[newsgroupNode linkedData]
readFlag:(ReadFlag)irFlag];
// use a temporary hashtable to map subjects to subject groups
tableOfSubjectGroups = [[HashTable allocFromZone:headersZone]
initKeyDesc:"*" valueDesc:"@"];
for (i = 0; (articleItem = [iArticleDB objectAt:i]) != nil; ++i) {
header = [articleItem objectWithKey:HEADER_INFO];
subject = [header infoForKey:SUBJECT];
// strip Re: from subject if present
if (strncmp(subject, "Re:", 3) == 0 || strncmp(subject, "re:", 3)
== 0 || strncmp(subject, "RE:", 3) == 0) {
subject += 3;
}
if (subject[0] == ' ') {
++subject;
}
articleNo = (int)[header infoForKey:ARTICLE_NUM];
sprintf(articleNoString, "%010d", articleNo);
if ((subjectGroup = [tableOfSubjectGroups valueForKey:subject])
== nil) {
// create a subject group for this subject
subjectGroup = [[IOrderedListD allocFromZone:headersZone]
initWithKey:subject];
[tableOfSubjectGroups insertKey:[subjectGroup key]
value:subjectGroup];
}
// add article to this subject group
[subjectGroup insertKeyedObject:(IKeyedObject *)articleItem];
}
newsgroupName = [newsgroupNode dataGroupName];
groupnameTmp = NXCopyStringBuffer(newsgroupName);
makeTreeKey(groupnameTmp, ikey, ".");
groupInfo = [[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO];
free (groupnameTmp);
tableState = [tableOfSubjectGroups initState];
listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:""];
while ([tableOfSubjectGroups nextState:&tableState
key:(const void **)&subject value:(void **)&subjectGroup]) {
articleItem = [subjectGroup objectAt:0];
header = [articleItem objectWithKey:HEADER_INFO];
articleNo = (int)[header infoForKey:ARTICLE_NUM];
sprintf(articleNoString, "%010d", articleNo);
if ([subjectGroup count] == 1) {
// create articleNode and link to article
articleNode = [[IUifNode allocFromZone:[self zone]]
initWithKey:articleNoString];
[articleNode setLinkedData:articleItem nodeType:Header];
// use subject as title
[articleNode setTitleForCell:[header infoForKey:SUBJECT]];
// set image for line num
if ((cline=(char *)[header infoForKey:LINES]) != NULL) {
// article header has "Lines:", so set mark to cell
image = [self getArticleMarkFor:(int)atoi(cline)];
[articleNode setImageForCell:image];
}
[articleNode setDataGroupName:newsgroupName];
[articleNode setLeaf:YES];
if ([groupInfo checkArticleIsRead:articleNo] == YES) {
[articleNode setActive:NO];
} else {
[articleNode setActive:YES];
}
[listnode insertKeyedObject:articleNode];
[subjectGroup removeObject:articleItem];
[subjectGroup free];
} else {
// More than one article in this subject group so create a subject
// node for this subject and link to subject group
subjectNode = [[IUifNode allocFromZone:[self zone]]
initWithKey:articleNoString];
[subjectNode setLinkedData:subjectGroup nodeType:SubjectGroup];
[subjectNode setTitleForCell:subject];
[subjectNode setDataGroupName:newsgroupName];
[subjectNode setLeaf:NO];
// check if there is unread article
isUnreadArticle = NO;
for (k=0; c_article=[subjectGroup objectAt:k]; ++k) {
if ([c_article isMemberOf:[IOrderedListD class]] &&
((c_header=[c_article dataForKey:HEADER_INFO]) != nil)) {
if ([groupInfo checkArticleIsRead:
(int)[c_header infoForKey:ARTICLE_NUM]] == NO) {
// there is an unread article
isUnreadArticle = YES;
break;
}
}
}
if (isUnreadArticle == YES) {
[subjectNode setActive:YES];
} else {
[subjectNode setActive:NO];
}
[listnode insertKeyedObject:subjectNode];
}
}
[tableOfSubjectGroups free];
return(listnode);
}
- _itemHeadersOfSubject:(IUifNode *)subjectNode
{
IOrderedListD *subjectGroup;
int i;
IOrderedListD *articleItem;
InfoD *header;
int articleNo;
char articleNoString[16];
IOrderedListD *listnode;
IUifNode *articleNode;
const char *newsgroupName;
// char groupnameTmp[256];
char *groupnameTmp;
char *cline;
id image;
subjectGroup = [subjectNode linkedData];
listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:""];
for (i = 0; (articleItem = [subjectGroup objectAt:i]) != nil; ++i) {
header = [articleItem objectWithKey:HEADER_INFO];
articleNo = (int)[header infoForKey:ARTICLE_NUM];
sprintf(articleNoString, "%010d", articleNo);
// create articleNode and link to article
articleNode = [[IUifNode allocFromZone:[self zone]]
initWithKey:articleNoString];
[articleNode setLinkedData:articleItem nodeType:Header];
// use sender as title
if ([subjectNode nodeType] == KeywordGroup) {
[articleNode setTitleForCell:[header infoForKey:SUBJECT]];
} else {
[articleNode setTitleForCell:[header infoForKey:FROM]];
}
// set image by line num
if ((cline=(char *)[header infoForKey:LINES]) != NULL) {
// article header has "Lines:", so set mark to cell
image = [self getArticleMarkFor:(int)atoi(cline)];
[articleNode setImageForCell:image];
}
newsgroupName = [subjectNode dataGroupName];
[articleNode setDataGroupName:newsgroupName];
[articleNode setLeaf:YES];
// add article node to subject group
//strncpy(groupnameTmp, newsgroupName, sizeof(groupnameTmp)-1);
groupnameTmp = NXCopyStringBuffer(newsgroupName);
makeTreeKey(groupnameTmp, ikey, ".");
if ([[[iNewsGroupTreeRoot nodeForKey:ikey] dataForKey:GROUPINFO]
checkArticleIsRead:articleNo] == YES) {
[articleNode setActive:NO];
} else {
[articleNode setActive:YES];
// also make the subject group active
[subjectNode setActive:YES];
}
free(groupnameTmp);
[listnode insertKeyedObject:articleNode];
}
return(listnode);
}
#define KEYWORD_LIST_SIZE 4096
- (IOrderedListD *)_keywordHeadersOf:(IUifNode *)newsgroupNode
{
const char *newsgroupName;
int i;
IOrderedListD *articleItem;
InfoD *header;
const char *keywords;
char keywordList[KEYWORD_LIST_SIZE], *ptr;
const char *keyword;
HashTable *tableOfKeywordGroups;
IOrderedListD *keywordGroup;
IOrderedListD *listnode;
IUifNode *keywordNode;
iArticleDB = [self initNewsGroup:[newsgroupNode linkedData]
readFlag:(ReadFlag)irFlag];
newsgroupName = [newsgroupNode dataGroupName];
// use a temporary hashtable to map subjects to subject groups
tableOfKeywordGroups = [[HashTable allocFromZone:headersZone]
initKeyDesc:"*" valueDesc:"@"];
listnode = [[IOrderedListD allocFromZone:[self zone]] initWithKey:""];
for (i = 0; (articleItem = [iArticleDB objectAt:i]) != nil; ++i) {
header = [articleItem objectWithKey:HEADER_INFO];
strncpy(keywordList, [header infoForKey:SUBJECT], sizeof(keywordList));
keywordList[sizeof(keywordList) - 1] = '\0';
if ((keywords = [header infoForKey:KEYWORDS]) != NULL) {
ptr = index(keywordList, '\0');
*ptr++ = ' ';
strncpy(ptr, keywords, &keywordList[sizeof(keywordList)]
- ptr);
keywordList[sizeof(keywordList) - 1] = '\0';
}
for (ptr = keywordList; *ptr != '\0'; ++ptr) {
if (isalnum(*ptr) == 0) {
*ptr = ' ';
} else if (isupper(*ptr) != 0) {
*ptr = tolower(*ptr);
}
}
for (keyword = strtok(keywordList, " "); keyword != NULL;
keyword = strtok(NULL, " ")) {
if (keyword[1] == '\0') {
continue;
}
if ([tableOfNoiseWords isKey:keyword] == YES) {
continue;
}
if ((keywordGroup = [tableOfKeywordGroups valueForKey:keyword])
== nil) {
// create a keyword group for this keyword
keywordGroup = [[IOrderedListD allocFromZone:headersZone]
initWithKey:keyword];
[tableOfKeywordGroups insertKey:[keywordGroup key]
value:keywordGroup];
keywordNode = [[IUifNode allocFromZone:[self zone]]
initWithKey:keyword];
[keywordNode setLinkedData:keywordGroup nodeType:KeywordGroup];
[keywordNode setTitleForCell:keyword];
[keywordNode setDataGroupName:newsgroupName];
[keywordNode setActive:YES];
[listnode insertKeyedObject:keywordNode];
}
// add article to this keyword group
[keywordGroup insertKeyedObject:(IKeyedObject *)articleItem];
}
}
[tableOfKeywordGroups free];
return(listnode);
}
@end